<?php namespace wanghua\general_utility_tools_php\gpt\chat;

use wanghua\general_utility_tools_php\phpmailer\Exception;


/**
 * 仅支持文本大模型调用，多模态模型暂不支持，请使用官方推荐的方式
 * Class ChatGPT
 * @package wanghua\general_utility_tools_php\gpt\chat
 */
class ChatGPT extends BaseChat
{
    public $url = '';
    public $apiKey = '';
    public $model = '';
    private $messages = [];
    public $post_msg_body;

    public function __construct()
    {
        //调用父级
        parent::__construct();
    }

    /**
     * desc：定制个性
     * eg:
     * [
        "role" => "system",
        "content" => "You are a helpful assistant."
        ]
     * author：wh
     * @param array $customize
     */
    function setCustomize($customize = [])
    {
        if ($customize) {
            foreach ($customize as $item) {
                $this->messages[] = $item;
            }
        }
    }

    /**
     * desc：对话之前的设置（前置配置）
     * eg:
     * [
        "role" => "user",
        "content" => "一些通用数据等内容"
        ]
     * author：wh
     * @param array $describe
     */
    function setBefore($describe = [])
    {
        if ($describe) {
            foreach ($describe as $item) {
                $this->messages[] = $item;
            }
        }
    }

    /**
     * desc：对话主体之后的设置（后置配置）（使用场景较少）
     * eg:
     * [
        "role" => "system",
        "content" => "You are a helpful assistant."
        ]
     * author：wh
     * @param array $describe
     */
    function setAfter($describe = [])
    {
        if ($describe) {
            foreach ($describe as $item) {
                $this->messages[] = $item;
            }
        }
    }

    private function check()
    {
        if (empty($this->url) || empty($this->apiKey) || empty($this->model)) {
            throw new Exception('PARAMS ERROR');
        }
    }

    /**
     * desc：【流式】对话，仅流式输出时调用该方法
     * 如需同步响应数据请使用returnAnswer方法
     *
    header('Content-Type: text/event-stream');
    header('Cache-Control: no-cache');
    header('Connection: keep-alive');
    header('X-Accel-Buffering: no');
     * 网络环境要求：建议生产环境建议使用HTTPS，部分浏览器限制混合内容
     *
     * author：wh
     * @param string $question
     * @param array $config
     * @param $answer_json_arr
     * @return string
     */
    function chat(string $question = '', array $config = [], &$answer_json_arr=[])
    {
        $answer = '';
        $this->curlPostChat($question, $config, function ($ch, $data) use ($question, &$answer, &$answer_json_arr) {
            $answer_json_arr[] = $data;
            $answer .= $data;
            echo $data;
            //echo str_pad('', 1024*70, ' ');//防止缓存(填充足够的缓存)
            ob_flush();
            flush();
            return strlen($data);
        });
        return $answer;
    }

    /**
     * desc：非流式请求对话
     * 不支持多模态，多模态请使用官方推荐的方式
     * author：wh
     * @param string $question
     * @param array $config
     * @param $answer_json_arr
     * @return string
     */
    function returnAnswer($question = '', $config = [], &$answer_json_arr)
    {
        $answer = '';
        $this->curlPostChat($question, $config, function ($ch, $data) use ($question, &$answer, &$answer_json_arr) {
            $answer_json_arr[] = $data;
            $answer .= $data;
            return strlen($data);
        });
        return $answer;
    }

    /**
     * desc：流式输出
     *
     * 不支持多模态，多模态请使用官方推荐的方式
     * author：wh
     * @param string $question
     * @param array $config
     * @param $callback
     */
    private function curlPostChat($question = '', $config = [], $callback)
    {
        header('Content-Type: text/event-stream');
        header('Cache-Control: no-cache, must-revalidate');
        header('X-Accel-Buffering: no'); // 防御 Nginx/Apache 缓冲
        header('Connection: keep-alive');
        $url = $this->url;
        $apiKey = $this->apiKey;
        $model = $this->model;
        $headers = ["Authorization: Bearer $apiKey", 'Accept: application/json', 'Content-Type: application/json',];
        $post_msg_body = ["model" => $model, 'stream' => true, 'chatId'=>$this->chatId];
        if ($config) {
            foreach ($config as $key => $val) {
                $post_msg_body[$key] = $val;
            }
        }
        if ($question) {
            $this->messages[] = ["role" => "user", "content" => $question];
        }
        $post_msg_body['messages'] = $this->messages;
        $this->post_msg_body = $post_msg_body;
        $postData = json_encode($post_msg_body);
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
        curl_setopt($ch, CURLOPT_WRITEFUNCTION, $callback);
        curl_exec($ch);
    }

    /**
     * desc： 获取对话结果，非流式输出时调用该方法
     * author：wh
     * @param string $question
     * @param array $config
     * @return array
     */
    function getchatgptresponse($question = '', $config = [])
    {
        $url = $this->url;
        $apiKey = $this->apiKey;
        $model = $this->model;
        $headers = ["Authorization: Bearer $apiKey", "Content-Type: application/json"];
        $post_msg_body = ["model" => $model, 'stream' => false];
        if ($config) {
            foreach ($config as $key => $val) {
                $post_msg_body[$key] = $val;
            }
        }
        if ($question) {
            $this->messages[] = ["role" => "user", "content" => $question];
        }
        $post_msg_body['messages'] = $this->messages;
        $this->post_msg_body = $post_msg_body;
        $post_msg_body = json_encode($post_msg_body);
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_msg_body);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        // 忽略 SSL 证书验证
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        $response = curl_exec($ch);
        if (curl_error($ch)) {
            return ['code' => curl_errno($ch), 'msg' => curl_error($ch)];
        } else {
            curl_close($ch);
            return ['code' => 200, 'msg' => 'cURL ok', 'data' => $response];
        }
    }

    private function parseData($data)
    {
        if (@json_decode($data)->choices[0]->message->content) {
            return json_decode($data)->choices[0]->message->content;
        }
        $data = str_replace('data: {', '{', $data);
        $data = rtrim($data, "\n\n");
        if (strpos($data, 'data: [DONE]') !== false) {
            return 'data: [DONE]';
        } else {
            if (false !== strpos($data, "\n\n")) {
                $exp_arr = explode("\n\n", $data);
                $str = '';
                try {
                    for ($i = 0; $i < count($exp_arr) - 1; $i++) {
                        $jsondecode_arr = json_decode($exp_arr[$i], true);
                        $str .= $jsondecode_arr['choices'][0]['delta']['content'];
                    }
                    return $str;
                } catch (\Exception $e) {
                    return $str;
                }
            }
            $data = @json_decode($data, true);
            if (!is_array($data)) {
                return '';
            }
            if ($data['choices'][0]['finish_reason'] == 'stop') {
                return 'data: [DONE]';
            } elseif ($data['choices'][0]['finish_reason'] == 'length') {
                return 'data: [CONTINUE]';
            }
            return $data['choices'][0]['delta']['content'] ?? '';
        }
    }
}